Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit 290f7e09d437502dfd03f66e3e656ebd94d3cec9


Parents : dc280c1
Author : Ivan <ivan@quad4.io>
Signature : Signature validation error
Date : 2026-04-13T22:58:04-05:00

feat(conversation-viewer): update message rendering with single emoji detection and dynamic font sizing for improved display

Changes
Diff

diff --git a/meshchatx/src/frontend/components/messages/ConversationViewer.vue b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
index 26a28f60..e942176f 100644
--- a/meshchatx/src/frontend/components/messages/ConversationViewer.vue
+++ b/meshchatx/src/frontend/components/messages/ConversationViewer.vue
@@ -818,10 +818,11 @@
'markdown-content--outbound-solid':
chatItem.is_outbound && !isThemeOutboundBubble(chatItem),
'markdown-content--inbound': !chatItem.is_outbound,
+ 'markdown-content--single-emoji': messageMarkdownSingleEmoji(chatItem),
}"
:style="{
'font-family': 'inherit',
- 'font-size': (config?.message_font_size || 14) + 'px',
+ 'font-size': messageMarkdownFontSizePx(chatItem) + 'px',
}"
@click="handleMessageClick"
v-html="renderMarkdown(chatItem.lxmf_message.content)"
@@ -2989,6 +2990,26 @@ export default {
renderMarkdown(text) {
return MarkdownRenderer.render(text);
},
+ messageMarkdownSingleEmoji(chatItem) {
+ const c = chatItem?.lxmf_message?.content;
+ if (!c) {
+ return false;
+ }
+ if (this.getParsedItems(chatItem)?.isOnlyPaperMessage) {
+ return false;
+ }
+ if (this.shouldHideAutoImageCaption(chatItem)) {
+ return false;
+ }
+ return MarkdownRenderer.isSingleEmojiMessage(c);
+ },
+ messageMarkdownFontSizePx(chatItem) {
+ const base = Number(this.config?.message_font_size) || 14;
+ if (this.messageMarkdownSingleEmoji(chatItem)) {
+ return Math.round(base * 2.75);
+ }
+ return base;
+ },
handleMessageClick(event) {
const nomadnetLink = event.target.closest(".nomadnet-link");
if (nomadnetLink) {
@@ -5724,6 +5745,15 @@ export default {
margin: 0.5rem 0;
}
+.markdown-content--single-emoji {
+ line-height: 1;
+}
+
+.markdown-content--single-emoji :deep(p) {
+ margin: 0;
+ line-height: 1;
+}
+
.markdown-content :deep(strong) {
font-weight: 700;
}

diff --git a/meshchatx/src/frontend/js/MarkdownRenderer.js b/meshchatx/src/frontend/js/MarkdownRenderer.js
index 2d873887..1c39d208 100644
--- a/meshchatx/src/frontend/js/MarkdownRenderer.js
+++ b/meshchatx/src/frontend/js/MarkdownRenderer.js
@@ -80,6 +80,30 @@ export default class MarkdownRenderer {
return processed_parts.join("\n");
}
+ /**
+ * True when the body is only a single emoji (after markdown strip), for large bubble rendering.
+ */
+ static isSingleEmojiMessage(raw) {
+ if (raw == null || typeof raw !== "string") {
+ return false;
+ }
+ let plain = MarkdownRenderer.strip(raw);
+ plain = plain.replace(/\s+/g, "");
+ if (!plain) {
+ return false;
+ }
+ if (typeof Intl === "undefined" || typeof Intl.Segmenter !== "function") {
+ return false;
+ }
+ const seg = new Intl.Segmenter(undefined, { granularity: "grapheme" });
+ const clusters = [...seg.segment(plain)].map((s) => s.segment);
+ if (clusters.length !== 1) {
+ return false;
+ }
+ const g = clusters[0];
+ return /\p{Extended_Pictographic}/u.test(g) || /\p{Emoji}/u.test(g);
+ }
+
/**
* Strips markdown from text for previews.
*/

diff --git a/tests/frontend/MarkdownRenderer.test.js b/tests/frontend/MarkdownRenderer.test.js
index 21856089..c8804c67 100644
--- a/tests/frontend/MarkdownRenderer.test.js
+++ b/tests/frontend/MarkdownRenderer.test.js
@@ -226,6 +226,22 @@ describe("MarkdownRenderer.js", () => {
});
});
+ describe("isSingleEmojiMessage", () => {
+ it("is true for one emoji and false for text or multiple emojis", () => {
+ expect(MarkdownRenderer.isSingleEmojiMessage("\u{1F600}")).toBe(true);
+ expect(MarkdownRenderer.isSingleEmojiMessage(" \u{1F600} ")).toBe(true);
+ expect(MarkdownRenderer.isSingleEmojiMessage("**\u{1F600}**")).toBe(true);
+ expect(MarkdownRenderer.isSingleEmojiMessage("\u{1F600}\u{1F600}")).toBe(false);
+ expect(MarkdownRenderer.isSingleEmojiMessage("hi")).toBe(false);
+ expect(MarkdownRenderer.isSingleEmojiMessage("\u{1F600} a")).toBe(false);
+ });
+
+ it("treats ZWJ family and skin tone as a single emoji", () => {
+ expect(MarkdownRenderer.isSingleEmojiMessage("\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}")).toBe(true);
+ expect(MarkdownRenderer.isSingleEmojiMessage("\u{1F44D}\u{1F3FD}")).toBe(true);
+ });
+ });
+
describe("strip", () => {
it("strips markdown correctly", () => {
const md = "# Header\n**Bold** *Italic* `code` ```\nblock\n```";


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────